package com.heaven.service.impl;

import com.heaven.config.ThreadPoolConfig;
import com.heaven.constant.BigFileConstant;
import com.heaven.constant.CommentConstants;
import com.heaven.constant.VideoConstants;
import com.heaven.core.dao.VideoDao;
import com.heaven.core.entity.UserEntity;
import com.heaven.core.entity.VideoCategoryEntity;
import com.heaven.core.entity.VideoEntity;
import com.heaven.customerServer.customerController.cacheEntity.PlayTimeCache;
import com.heaven.customerServer.customerController.vo.login.LoginRes;
import com.heaven.customerServer.customerController.vo.video.*;
import com.heaven.core.framework.PzServiceImpl;
import com.heaven.exception.BizCodeEnum;
import com.heaven.exception.RRException;
import com.heaven.service.*;
import com.heaven.service.minio.MinIoService;
import com.heaven.utils.*;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Service("videoService")
@Slf4j
public class VideoServiceImpl extends PzServiceImpl<VideoDao, VideoEntity> implements VideoService {
    @Resource
    public MinIoService minIoService;
    @Resource
    private UserService userService;
    @Resource
    private VideoCategoryService videoCategoryService;
    @Resource
    private DanmuService danmuService;
    @Resource
    private CommentService commentService;

    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private RedisUtils redisUtils;

    @Override
    public void publishVideo(PublishVideoReq req, String userId) {
        VideoEntity video = new VideoEntity();
        BeanUtils.copyBeanProp(video, req);
        video.setUserId(userId);

        // TODO 暂时设置为上传就可公共访问状态
        video.setStatus(VideoConstants.VIDEO_PUBLISH_STATUS);

        String fileObject = BigFileConstant.getBigFileObjectName(req.getMd5(), "."+req.getFormat());
        if (isVideoExistInMinio(fileObject)){
            video.setIsInMinio(1);
        } else {
            // TODO 检验视频文件分块缓存是否存在在服务器上，单机部署情况下检查本地，分布式情况下检查minio分块
            video.setIsInMinio(0);
        }
        save(video);
    }

    @Override
    public PlayVideoByIdRes getPlayVideoById(String id, boolean loginStatus, LoginRes.UserSession userSession) {
        VideoEntity temp = selectById(id);
        if (temp == null) {
            throw new RRException(BizCodeEnum.VIDEO_NOT_EXIST, id);
        }
        VideoEntity videoEntity = initVideo(temp);
        if(videoEntity == null) {
            return null;
        }
        // 视频发布者的基本信息
        UserEntity userEntity = userService.selectById(videoEntity.getUserId());
        userEntity = userService.initUser(userEntity);

        PlayVideoByIdRes res = new PlayVideoByIdRes();
        BeanUtils.copyBeanProp(res, videoEntity);
        res.setVideoIntroduce(videoEntity.getIntroduce());
        BeanUtils.copyBeanProp(res, userEntity);
        res.setUserIntroduce(userEntity.getIntroduce());
        // 视频分类名称
        res.setCategoryName(getVideoCategoryName(videoEntity.getCatId()));
        // 登录用户视频点赞、收藏状态

        //加读锁，执行业务
        RLock rLikeLock = redissonClient.getReadWriteLock(VideoConstants.VIDEO_LIKE_RW_LOCK+videoEntity.getVideoId()).readLock();
        if(!loginStatus) {
            res.setIsLike(0);
            res.setIsCollect(0);
        } else {
            res.setIsLike(videoEntity.getBeLikedUserIds().contains(userSession.getUserId()) ? 1 : 0);
            res.setIsCollect(videoEntity.getBeCollectedUserIds().contains(userSession.getUserId()) ? 1 : 0);
        }
        res.setLikeCount((long) videoEntity.getBeLikedUserIds().size());
        rLikeLock.unlock();

        res.setCollectCount((long) videoEntity.getBeCollectedUserIds().size());

        // 视频弹幕量
        Long danmuCount = danmuService.selectCount(new Query(Criteria.where("video_id").is(res.getVideoId())));
        res.setDanmuCount(danmuCount);

        // 视频评论量
        Long commentCount = commentService.selectCount(
                Query.query(Criteria.where("target_id").is(res.getVideoId())
                .and("type").is(CommentConstants.COMMENT_TYPE_VIDEO)));
        res.setCommentCount(commentCount);

        return res;
    }

    @Override
    public VideoEntity initVideo(VideoEntity video) {
        if(video == null) {
            return null;
        }
        if(video.getUrl() != null && video.getUrl().contains(":")) {
            String[] split1 = video.getUrl().split(":");
            String url = minIoService.getObjectUrl(split1[0], split1[1], 1, TimeUnit.DAYS);
            video.setUrl(url);
        }
        if(video.getCoverPicture() != null && video.getCoverPicture().contains(":")) {
            String[] split2 = video.getCoverPicture().split(":");
            String coverPictureUrl = minIoService.getObjectUrl(split2[0], split2[1], 1, TimeUnit.DAYS);
            video.setCoverPicture(coverPictureUrl);
        }

        return video;
    }

    @Override
    public String getVideoCategoryName(String catId) {
        String[] cids = catId.split("-");
        // 三级分类
        VideoCategoryEntity categoryLevel1 = videoCategoryService.selectById(cids[0]);
        if(categoryLevel1 == null) {
            return "";
        }
        VideoCategoryEntity categoryLevel2 = categoryLevel1.getChildren().get(Integer.parseInt(cids[1])-1);
        if(categoryLevel2 == null) {
            return categoryLevel1.getName();
        }
        VideoCategoryEntity categoryLevel3 = categoryLevel2.getChildren().get(Integer.parseInt(cids[2])-1);
        return Objects.requireNonNullElse(categoryLevel3, categoryLevel2).getName();
    }

    @Override
    public List<RecommendVideosRes> recommendVideos() {
        List<RecommendVideosRes> ans = new ArrayList<>();
        Query query = new Query();
        query.addCriteria(Criteria.where("status").is(VideoConstants.VIDEO_PUBLISH_STATUS).and("is_in_minio").is(1));
        query.with(Sort.by(Sort.Direction.DESC, "publish_time"));
        List<VideoEntity> videoEntities = selectList(query, 3, 1);
        List<CompletableFuture<Void>> tasks = new ArrayList<>();
        for (final VideoEntity videoEntity : videoEntities) {
            CompletableFuture<Void> task = CompletableFuture.runAsync(() -> {
                VideoEntity video = initVideo(videoEntity);
                RecommendVideosRes temp = new RecommendVideosRes();
                BeanUtils.copyBeanProp(temp, video);

                temp.setCategoryName(getVideoCategoryName(video.getCatId()));

                UserEntity userEntity = userService.selectById(temp.getUserId());
                userEntity = userService.initUser(userEntity);
                BeanUtils.copyBeanProp(temp, userEntity);
                temp.setDanmuCount(danmuService.selectCount(new Query(Criteria.where("video_id").is(temp.getVideoId()))));

                ans.add(temp);
            }, threadPoolTaskExecutor);
            tasks.add(task);
        }
        ThreadPoolConfig.blockTask(tasks);
        return ans;
    }

    @Override
    //@Cacheable(value = "OWN_VIDEOS", key = "#root.args[2]")
    public List<OwnVideoRes> listOwnVideos(int page, int size, String userId) {
        List<OwnVideoRes> ans = new ArrayList<>();
        List<VideoEntity> videoEntities = selectList(new Query(Criteria.where("user_id").is(userId)), size, page);
        for (VideoEntity videoEntity : videoEntities) {
            videoEntity = initVideo(videoEntity);
            String categoryName = getVideoCategoryName(videoEntity.getCatId());
            OwnVideoRes temp = new OwnVideoRes();
            BeanUtils.copyBeanProp(temp, videoEntity);
            temp.setCategoryName(categoryName);

            // 搜索评论数
            Long commentCount = commentService.selectCount(Query.query(Criteria
                    .where("target_id").is(temp.getVideoId())));
            temp.setCommentCount(commentCount);

            // 搜索点赞数
            temp.setLikeCount(videoEntity.getBeLikedUserIds().size());

            // 搜索收藏数
            temp.setCollectCount(videoEntity.getBeCollectedUserIds().size());

            // 搜索弹幕数
            Long danmuCount = danmuService.selectCount(Query.query(Criteria
                    .where("video_id").is(temp.getVideoId())));
            temp.setDanmuCount(danmuCount);

            ans.add(temp);
        }
        return ans;
    }

    @Override
    public int handleLike(String videoId, String userId) {
        int ans;
        VideoEntity videoEntity = selectById(videoId);
        if(videoEntity == null) {
            throw new RRException(BizCodeEnum.VIDEO_NOT_EXIST,videoId);
        }
        boolean flag = videoEntity.getBeLikedUserIds().contains(userId);
        if (flag) {
            // 取消点赞
            videoEntity.getBeLikedUserIds().remove(userId);
            ans = 0;
        } else {
            // 点赞
            videoEntity.getBeLikedUserIds().add(userId);
            ans = 1;
        }
        Query query = new Query(Criteria.where("_id").is(videoId));
        Update update = Update.update("be_liked_user_ids", videoEntity.getBeLikedUserIds());

        RReadWriteLock rwLock = redissonClient.getReadWriteLock(VideoConstants.VIDEO_LIKE_RW_LOCK+videoId);
        //加锁，执行业务
        RLock wLock = rwLock.writeLock();
        wLock.lock();
        updateOne(query, update);
        wLock.unlock();
        return ans;
    }

    @Override
    public String handlePlayCount(PlayCountReq req) {
        VideoEntity videoEntity = selectById(req.getVideoId());
        if(videoEntity == null) {
            throw new RRException(BizCodeEnum.VIDEO_NOT_EXIST, req.getVideoId());
        }
        PlayTimeCache playTimeCache = null;
        String key = "";

        if(req.getType() != VideoConstants.PLAY_COUNT_REQ_TYPE_TOKEN) {
            // 请求参数必定携带 token
            key = VideoConstants.PLAY_COUNT_TOKEN_KEY_PREFIX + req.getToken();
            playTimeCache = redisUtils.getCacheObject(key);
            if(playTimeCache == null) {
                throw new RRException(BizCodeEnum.VAILD_EXCEPTION, req.getToken()+ " -> 此token无效");
            }
        }
        switch (req.getType()) {
            case VideoConstants.PLAY_COUNT_REQ_TYPE_TOKEN:
                // 视频刚加载完时，请求获取播放token
                String playToken = UUIDUtils.randomUUID().toString();
                // 存入redis
                playTimeCache = new PlayTimeCache(playToken, new Date(), req.getVideoId(), null, 0);
                redisUtils.setCacheObject(VideoConstants.PLAY_COUNT_TOKEN_KEY_PREFIX+playToken, playTimeCache, 12, TimeUnit.HOURS);
                return playToken;
            case VideoConstants.PLAY_COUNT_REQ_TYPE_START:
                // 视频开始播放时触发
                playTimeCache.setPlayTimeNode(new Date());
                playTimeCache.setTotalTime(0);
                redisUtils.setCacheObject(key, playTimeCache);
                break;
            case VideoConstants.PLAY_COUNT_REQ_TYPE_PLAY:
                // 视频每次播放就触发
                playTimeCache.setPlayTimeNode(new Date());
                redisUtils.setCacheObject(key, playTimeCache);
                break;
            case VideoConstants.PLAY_COUNT_REQ_TYPE_PAUSE:
                // 视频每次暂停就触发
                // 累加播放时间
                int temp = playTimeCache.getTotalTime() + DateUtils.differentSecondsByMillisecond(new Date(), playTimeCache.getPlayTimeNode());
                playTimeCache.setTotalTime(temp);
                redisUtils.setCacheObject(key, playTimeCache);
                break;
            case VideoConstants.PLAY_COUNT_REQ_TYPE_ENDED:
                // 退出视频播放页面就触发
                // 结算播放时间, 当播放时间大于等于视频总时长的 3/4 时，给播放数加1
                boolean flag = playTimeCache.getPlayTimeNode() == null;

                if(!flag) {
                    int total = playTimeCache.getTotalTime() + DateUtils.differentSecondsByMillisecond(new Date(), playTimeCache.getPlayTimeNode());
                    if (total <= videoEntity.getDuration() && total >= (videoEntity.getDuration() * 0.75)) flag = true;
                }

                if(flag) {
                    RLock wLock = redissonClient.getReadWriteLock(VideoConstants.PLAY_COUNT_RW_LOCK + req.getVideoId()).writeLock();
                    wLock.lock();
                    videoEntity.setPlayCount(videoEntity.getPlayCount().add(new BigDecimal(1)));
                    updateById(videoEntity.getVideoId(), videoEntity);
                    wLock.unlock();
                }
                redisUtils.deleteObject(key);
                break;
            default:
                throw new RRException(BizCodeEnum.VAILD_EXCEPTION, "playCountReq type error");
        }
        return req.getToken();
    }

    @Override
    public VideoEntity selectById(String videoId) {
        RLock rLock = redissonClient.getReadWriteLock(VideoConstants.VIDEO_LIKE_RW_LOCK + videoId).readLock();
        rLock.lock();
        VideoEntity videoEntity = super.selectById(videoId);
        rLock.unlock();
        return videoEntity;
    }

    @Override
    public boolean isVideoExistInMinio(String videoObjectName) {
        return minIoService.isObjectExist(minIoService.bucketName, videoObjectName);
    }
}
